feat(config): llm.runMode block — local | cloud | fusion with a fusion cloud-share dial - #161
feat(config): llm.runMode block — local | cloud | fusion with a fusion cloud-share dial#161plombeer31 wants to merge 2 commits into
Conversation
…n cloud-share dial Adds the config foundation for an operator run mode without changing any behaviour: nothing calls `resolveRunMode` outside its tests yet. `llm.runMode` sits beside `llm.fallback` rather than under `agent` (loop budgets) or at the top level: `llm` has a single writer, and the mode and `activeTextProvider` must move in one write + cache-reset cycle or they can disagree. `activeTextProvider` stays authoritative — `runMode.mode` is additive. The effective mode is derived from which provider is active, and a stored `fusion` is honoured only when the cloud leg is the active one. So an operator who switches provider by hand in Manage → LLM simply drops out of fusion on the next read: no reconciliation step, and no state that lies about what is running. It also means fusion pins the cloud provider as the fallback chain's primary, so `resolveFallbackChain` hoists it to the head and appends local at the tail with no changes of its own. `fusion.cloudShare` is documented as a dial, not a quota: it moves the cutoff on a bounded per-step complexity score rather than promising that N% of steps reach the cloud. Degradation is explicit and reported rather than silent — cloud/fusion without a cloud provider stays local, fusion without a local provider runs cloud-only, and a pinned `toolTransport` warns without downgrading. USER_CONFIG_VERSION 37 → 38. No migration code: `runMode` is an optional sub-key of an already-optional block, so absence is exactly the v37 behaviour; the bump only records the schema change. Verified: npm run lint and npm run build clean; npm test 4094 passed / 8 failed, the same 6 files / 8 tests that already fail on main @ 667dae1 (stale banner + tui-app fixtures, the `localModels.embeddings.url` fixture, a dev-machine-specific fs-glob path, and send-message-concurrency).
…mmand This branch takes USER_CONFIG_VERSION to 38. Hand testing showed what that does to a machine that also has the installed v0.2.2 release: they share one ~/.atomic-agent/config.json, so the moment the newer build wrote v38, every single v0.2.2 command died with ConfigValidationError: invalid config: version: unsupported config version 38; expected one of 5, 6, ... 37 — models status, config get, the TUI, all of it, with no way out short of hand-editing the file. Running a release and a build under test side by side is normal, and the version bump made them mutually exclusive. Every bump this schema has taken is additive: the parser reads field by field and writeUserConfigFileSync preserves unknown top-level keys. So a newer file already parses correctly — the allow-list only ever needed a floor, not a ceiling. Older-than-supported is still refused, and a non-integer version is now refused explicitly rather than falling through the membership test. ensureUserConfigFileSync gets the other half: it must not rewrite a newer file back down to this build's shape. Reading it is safe; overwriting would delete the newer build's keys, and with a shared file the two builds would take turns destroying each other's config on every launch. Knowingly replaces the "rejects unsupported version" test, which pinned the broken behaviour.
|
Superseded by the Fusion stack rebuilt on v0.5.6: #365 (config + resolver) → #371 (composer-switch row) → #374 (configurators) → #375 (runtime seams) → #378 ( That stack is a different feature from this one: fusion there is a cloud orchestrator that plans and delegates to concurrent local worker agents, rather than per-step complexity routing, so the Closing rather than rebasing: these branches fork from v0.2.2, ~440 commits back, and the menu and mouse work #172/#173 carried has since landed separately (#165, #275–#282). |
Stack: 1 of 3. Foundation only — nothing calls
resolveRunModeoutside its tests, so this changes no behaviour.llm.runModeconfig + resolver ← you are herefeat/fusion-routing— the routing engine (based on this branch)feat/run-mode-tui— the Run submenu + docsWhy
Switching between the local llama-server model and a cloud provider means walking into Manage → LLM, picking a pane and selecting a model row — several keystrokes deep in a panel you have to leave the chat to reach. The goal of the stack is a submenu on Run with three entries (Local · Cloud · Fusion) that flips models in one gesture, plus a Fusion mode that orchestrates on the cloud model and executes on the local one.
Where the block lives, and why
llm.runMode, a sibling ofllm.fallback— not underagent(which is loop budgets) and not top-level (whenllmis absent the runtime synthesizes a singlelocal-llamaprovider, so onlylocalis reachable anyway).The decisive reason is atomicity:
llmhas a single writer, and the mode andactiveTextProviderhave to move in onewriteUserConfigFileSync+resetConfigCache()cycle or they can disagree.The non-contradiction rule
llm.activeTextProviderstays authoritative;runMode.modeis additive:Consequences, all deliberate:
resolveFallbackChainhoists it to the chain head and appends local at the tail — sofallback-config.tsneeds no changes and its locked invariants stay green.cloudShareis a dial, not a quotaIt moves a cutoff on a bounded per-step complexity score (PR 2); it does not promise that N% of steps reach the cloud.
0behaves exactly likelocal,100exactly likecloud. That is stated in the JSDoc and (in PR 3) in AGENTS.md, so nobody later "fixes" it into a running-counter scheduler — a quota necessarily sends some trivial steps to the cloud and keeps some hard ones local, the opposite of the intent.Degradation is reported, never silent
localcloudtoolTransportpinnedfusionConfig version
USER_CONFIG_VERSION37 → 38, with no migration code:runModeis an optional sub-key of an already-optional block, so absence is exactly the v37 behaviour. The bump only records the schema change, per the AGENTS.md rule. Pinned by a test that a v37 file upgrades untouched.Verification
npm run lintandnpm run buildclean.npm test: 4094 passed, 8 failed — the same 6 files / 8 tests that already fail onmain @ 667dae1(verified by re-running them on a clean checkout of main): stale splash-banner and tui-app fixtures, thelocalModels.embeddings.urlfixture, a dev-machine-specificfs-globpath, andsend-message-concurrency. No new failures.29 new tests across
llm-run-mode-config.test.ts,resolve-run-mode.test.tsandrun-mode-degradation.test.ts, covering the full degradation matrix and the non-contradiction rule in both directions.